﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Collections.Generic;
using System.Windows.Threading;
using Utils;
using System.Linq;
using System.Text;
using UIObjects;
using System.Text.RegularExpressions;

namespace Hydra.Shell.DelayObjects
{
    public class DelayedObjectArgs<T> : EventArgs
    {
        public T RealObject { get; set; }

        public DelayedObjectArgs(T realObject)
        {
            this.RealObject = realObject;
        }
    }

    public class DelayedExceptionArgs : EventArgs
    {
        public Exception Exception { get; set; }

        public DelayedExceptionArgs(Exception exception)
        {
            this.Exception = exception;
        }
    }

    public class DelayedStatusArgs : EventArgs
    {
        public string StatusText { get; set; }
        public int ProgressComplete { get; set; }
        public int ProgressTotal { get; set; }

        public DelayedStatusArgs(string statusText, int progressComplete, int progressTotal)
        {
            this.StatusText = statusText;
            this.ProgressComplete = progressComplete;
            this.ProgressTotal = progressTotal;
        }
    }

    public class DelayedObjectBase<T> : IDelayedTypedObject<T> 
    {
        protected T innerObject;
        protected DispatcherTimer delayTimer;
        protected ILog log;
        public event EventHandler<DelayedObjectArgs<T>> ObjectReady;
        public event EventHandler<DelayedExceptionArgs> OnException;
        public event EventHandler<DelayedStatusArgs> OnStatus;
        public Exception Exception;
        public bool IsReady { get; private set; }
        public DelayedObjectBase<T> WrappedParent;
        public DelayedObjectBase<T> WrappedChild;
        public Queue<DelayedObjectHistoryItem> HistoryQueue;
        public TimeSpan? DelayTimeout { get; private set; }
        public event DelayedObjectHistoryHandler TimeOutExceptionOccurred;
        public event DelayedObjectHistoryHandler HistoryItemReported;
        public static bool DisableTimout = true;

        protected DelayedObjectBase(TimeSpan? delayTimeout = null)
        {
            if (delayTimeout != null)
            {
                delayTimer = new DispatcherTimer();

                if (Debugger.IsAttached)
                {
                    delayTimer.Interval = (TimeSpan)delayTimeout;
                }
                else
                {
                    delayTimer.Interval = ((TimeSpan)delayTimeout).Add((TimeSpan)delayTimeout);
                }

                delayTimer.Tick += (sender, e) =>
                {
                    var timeOut = delayTimeout;
                    var historyItem = new DelayedObjectHistoryItem
                    {
                        HistoryDescription = "Delayed object lifetime timeout",
                        DelayedObject = (IDelayedObject) this,
                        StackTrace = new StackTrace().ToString(),
                        Timestamp = DateTime.Now,
                        TimeoutIfNoFollowupHistory = delayTimeout
                    };

                    if (WrappedParent != null)
                    {
                        WrappedParent.RaiseTimeOutExceptionOccurred(historyItem);
                    }
                    else if (!DisableTimout)
                    {
                        Debugger.Break();

                        if (TimeOutExceptionOccurred != null)
                        { 
                            TimeOutExceptionOccurred(this, new EventArgs<DelayedObjectHistoryItem>(historyItem));
                        }
                    }
                };

                this.DelayTimeout = delayTimeout;

                delayTimer.Start();
            }
        }

        public DelayedObjectBase(UIObjects.ILog log, TimeSpan? delayTimeout) : this(delayTimeout)
        {
            this.log = log;

            log.InterfaceLogEntrySent += (sender, e) =>
            {
                var data = e.LogEntryData.ToString();
                var regex = new Regex(@"ObjectType\:.*?, Description:.*?, Timestamp:.*?, StackTrace");

                if (!regex.IsMatch(data))
                {
                    ReportHistory(data, true);
                }
            };

            //log.InterfaceWriteRequested += (sender, e) =>
            //{
            //    var data = e.LogEntryData.ToString();
            //    var regex = new Regex(@"ObjectType\:.*?, Description:.*?,");

            //    if (!regex.IsMatch(data))
            //    {
            //        ReportHistory(e.LogEntryData.ToString(), true);
            //    }
            //};

            //log.InterfaceInternalMessage += (sender, e) =>
            //{
            //    if (e.Exception != null)
            //    {
            //        ReportHistory(e.Exception, true);
            //    }
            //    else
            //    {
            //        ReportHistory(e.Message, true);
            //    }
            //};
        }

        public T InnerObject
        {
            get 
            {
                return innerObject;
            }
        }

        public bool ExceptionOccurred
        {
            get
            {
                return Exception != null;
            }
        }

        private void RaiseOnStatus(string statusText, int progressComplete, int progressTotal)
        {
            if (this.WrappedParent != null)
            {
                this.WrappedParent.RaiseOnStatus(statusText, progressComplete, progressTotal);
            }
            else
            {
                if (OnStatus != null)
                {
                    OnStatus(this, new DelayedStatusArgs(statusText, progressComplete, progressTotal));
                }
            }
        }

        public void SetStatus(string statusText, int progressComplete, int progressTotal)
        {
            this.RaiseOnStatus(statusText, progressComplete, progressTotal);
        }

        public void SetException(Exception exception)
        {
            this.ReportHistory(exception);
            this.Exception = exception;

            if (this.WrappedParent != null)
            {
                this.WrappedParent.SetException(exception);
            }

            if (OnException != null)
            {
                OnException(this, new DelayedExceptionArgs(exception));
            }
        }

        protected void RaiseTimeOutExceptionOccurred(DelayedObjectHistoryItem item)
        {
            if (TimeOutExceptionOccurred != null)
            {
                TimeOutExceptionOccurred(this, new EventArgs<DelayedObjectHistoryItem>(item));
            }

            if (OnException != null)
            {
                OnException(this, new DelayedExceptionArgs(new DelayedObjectTimeoutException(item)));
            }

            if (WrappedParent != null)
            {
                WrappedParent.RaiseTimeOutExceptionOccurred(item);
            }
        }

        protected void ThrowIfNotReady()
        {
            Debug.Assert(IsReady, "Delayed object not ready for use.");
        }

        public string HistoryText
        {
            get
            {
                var text = new StringBuilder();
                List<DelayedObjectHistoryItem> history = this.History;

                history.ForEach(i =>  text.AppendFormat("********************** \r\n\r\n {0} \r\n\r\n", i.ToString()));

                return text.ToString();
            }
        }

        public void AppendHistory(Queue<DelayedObjectHistoryItem> listToAppendTo)
        {
            this.HistoryQueue.ToList().ForEach(i => 
            {
                listToAppendTo.Enqueue(i);
            });
        }

        public List<DelayedObjectHistoryItem> History
        {
            get
            {
                if (WrappedParent != null)
                {
                    return WrappedParent.History;
                }
                else
                {
                    return HistoryQueue.Reverse().ToList();
                }
            }
        }

        public void ReportHistory(Exception exception, bool noLog)
        {
            var historyDescription = "Exception occurred: " + exception.Message + "\r\n" + exception.StackTrace;

            ReportHistory(historyDescription, null, noLog);
        }

        public void ReportHistory(Exception exception)
        {
            var historyDescription = "Exception occurred: " + exception.Message + "\r\n" + exception.StackTrace;

            ReportHistory(historyDescription);
        }

        public void ReportHistory(Exception exception, string nextStep)
        {
            var historyDescription = "Exception occurred: " + exception.Message;

            ReportHistory(historyDescription);
            ReportHistory(nextStep);
        }

        public void ReportHistory(ReportHistoryType reportHistoryType, params object[] parms)
        {
            var description = reportHistoryType.GetDescription(parms);
            TimeSpan? timeout;

            if (reportHistoryType.HasTimeout())
            {
                timeout = reportHistoryType.GetTimeout(parms);
                this.ReportHistory(description, timeout);
            }
            else
            {
                this.ReportHistory(description);
            }
        }

        public void ReportHistory(string formatString, params object[] parms)
        {
            var description = string.Format(formatString, parms);

            this.ReportHistory(description);
        }

        public void ReportHistory(string historyDescription, bool noLog)
        {
            this.ReportHistory(historyDescription, null, noLog);
        }

        public void ReportHistory(string historyDescription, TimeSpan? timeoutIfNoFollowUpHistory = null, bool noLog = false)
        {
            if (WrappedParent != null)
            {
                WrappedParent.ReportHistory(historyDescription, timeoutIfNoFollowUpHistory);
            }
            else
            {
                if (HistoryQueue == null)
                {
                    HistoryQueue = new Queue<DelayedObjectHistoryItem>();
                }

                var historyItem = new DelayedObjectHistoryItem
                {
                    HistoryDescription = historyDescription,
                    DelayedObject = (IDelayedObject) this,
                    StackTrace = noLog ? string.Empty : new StackTrace().ToString(),
                    Timestamp = DateTime.Now,
                    TimeoutIfNoFollowupHistory = timeoutIfNoFollowUpHistory
                };

                var lastItem = HistoryQueue.LastOrDefault();

                if (lastItem != null && lastItem.Timer != null)
                {
                    lastItem.Timer.Stop();
                }

                HistoryQueue.Enqueue(historyItem);
                //Debug.WriteLine(historyItem.ToString());

                if (!noLog && log != null)
                {
                    log.Info(historyItem.ToString());
                }

                if (timeoutIfNoFollowUpHistory != null)
                {
                    var timeout = (TimeSpan)timeoutIfNoFollowUpHistory;
                    var timer = new DispatcherTimer();

                    timer.Interval = timeout;

                    timer.Tick += (sender, e) =>
                    {
                        if (!DisableTimout)
                        {
                            RaiseTimeOutExceptionOccurred(historyItem);
                        }
                    };
                }

                if (HistoryItemReported != null)
                {
                    HistoryItemReported(this, new EventArgs<DelayedObjectHistoryItem>(historyItem));
                }
            }
        }

        public void Wrap(DelayedObjectBase<T> delayedObject)
        {
            WrappedChild = delayedObject;
            delayedObject.WrappedParent = this;

            delayedObject.log = this.log;

            if (WrappedChild.History.Count > 0)
            {
                WrappedChild.AppendHistory(this.HistoryQueue);
            }
        }

        public void SetReady(T innerObject)
        {
            this.IsReady = true;
            this.innerObject = innerObject;

            if (this.WrappedParent != null)
            {
                this.WrappedParent.SetReady(innerObject);
            }

            if (delayTimer != null)
            {
                delayTimer.Stop();
            }

            if (ObjectReady != null)
            {
                ObjectReady(this, new DelayedObjectArgs<T>(innerObject));
            }
        }
    }
}
